/**
 *
 */
package asm32;

import java.io.*;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.ArrayList;

/**
 * The main assembler class.
 * Takes an input file name as a parameter. Creates an output file with the
 * same file name but a .out extension.
 * @author Piotr
 *
 */
public class Assembler {

        private static File sourceFile;
        private static File outputFile;
        private static PrintWriter outputWriter;
        private static TreeMap<String, Integer> symbolTable;
        private static ArrayList<Object[]> branchTable;
        private static TreeMap<String, String> opcodeTable;
        private static TreeMap<String, String> functTable;
        private static TreeMap<String, Integer> regAliasTable;
        private static ArrayList<String> outputText;
       
        /**
         * Main function. Takes input file name as parameter.
         * @param args The input file name.
         */
        public static void main(String[] args) throws IOException {

                initTables();
               
       
                // Try to open the source file
                if (args.length > 0) {
                        sourceFile = new File(args[0]);
                        if (!sourceFile.exists()) {
                                System.out.println("File \"" + args[0] + "\" does not exist");
                                System.exit(1);
                        }
         
                createOutputFile(args[0]);
                translateFile();
                writeOutputFile();              //Throws IOException
                       
                outputWriter.close();   //Throws IOException
                } else {
                        System.out.println("No input file specified");
                        System.exit(1);
                }
        }
       




        /**
         * Writes the contents of the outputText array out to the output file.
         * @throws IOException
         */
        private static void writeOutputFile() throws IOException {
                int lineNum = 0;
                for (String line: outputText ) {
                        outputWriter.println(lineNum + "\t" + line);
                        lineNum++;
                }
                outputWriter.flush();
        }






        /**
         * Attempts to create an output file with the same name as the input file
         * but with a .out extension. (stripping the original extension)
         */
        private static void createOutputFile(String inputFileName) {
                String outputFileName;
                //Scan the input file name from the end, to identify the extension
                //Assume a blank extension if the beginning of the string is reached
                //or a pathname delimiter.
                int ii = inputFileName.length();
                char ch;
                do {
                        ii--;
                        ch = inputFileName.charAt(ii);
                } while (ii > 0 && ch != '.' && ch != '/' && ch != '\\');
                if (ii == 0 || ch == '/' || ch == '\\') {
                        outputFileName = inputFileName + ".out";
                } else {
                        outputFileName = inputFileName.substring(0, ii) + ".out";
                }
                //System.out.println(outputFileName);
                outputFile = new File(outputFileName);
                try {
                        outputWriter = new PrintWriter(outputFile);
                } catch (IOException e) {
                        System.out.println("Error creating output file \"" + outputFileName + "\"");
                        System.exit(1);
                }
        }

        /**
         * Subroutine that performs the overall function of translating the input file
         * in assembly to an output file in machine code.
         */
        private static void translateFile() throws IOException {
                symbolTable = new TreeMap<String, Integer>();
                branchTable = new ArrayList<Object[]>();
                outputText = new ArrayList<String>();
                //Read input file in line-by-line, create output file and branch/symbol tables
                doFirstPass();
                doSecondPass();
               
        }
       
        /**
         * Method to perform the second pass of the assembler.
         * Iterates through the branch table, calculates the branch targets using
         * the symbol table, and fills in the target addresses.
         */
        private static void doSecondPass() {
                for (Object[] entry: branchTable) {
                        int source = (Integer)entry[0];
                        String label = (String)entry[1];
                        if (symbolTable.containsKey(label)) {
                                int target = symbolTable.get(label);
                                int offset = target - (source + 1);
                                String offsetStr = toBits(offset, 16, true);
                                if (offsetStr == null) {
                                        System.out.println("Error: Label \"" + label +
                                        "\" out of range.");
                                }
                                String code = outputText.get(source);
                                code = code.replace("#target#", offsetStr);
                                outputText.set(source, code);
                        } else {
                                System.out.println("Error: Label \"" + label +
                                "\" not found.");
                        }
                       
                       
                }
               
        }






        /**
         * Subroutine to do the first pass of the assembler.
         * @return      The number of lines in the output file.
         * @throws FileNotFoundException
         */
        private static int doFirstPass() throws FileNotFoundException {
                Scanner scanIn = new Scanner(sourceFile);
                String line;
                int inLineCount = 0;
                int outLineCount = 0;
                while (scanIn.hasNext()) {
                        line = scanIn.nextLine();
                        inLineCount++;
                        outLineCount = doFirstPassLine(inLineCount, outLineCount, line);
                }
                return outLineCount;
        }
       
        /**
         * Subroutine to do the first-pass processing on a line from the input file
         * @param inLineCout The current line of the input file
         * @param outLineCount The current line count in the output file
         * @param line The String of the line from the input to be processed
         * @return lineCount incremented by the number of lines that were added to the output
         */
        private static int doFirstPassLine(int inLineCnt, int lineCount, String line) {
                Scanner scanLine = new Scanner(line);
                String token;
                if (!scanLine.hasNext()) {
                        return lineCount;       //The string is empty
                } else {
                        //Check the beginning of a line for a label
                        //Any word followed by a colon found at the beginning of the line is
                        //treated as a label.
                        if ((token = scanLine.findInLine("^\\s*\\w+\\s*:")) != null) {
                                token = token.split(":")[0];
                                token = token.trim();
                                //System.out.println("Found label: " + token);
                                //Check if the label is a mnemonic
                                if (opcodeTable.containsKey(token.toLowerCase())) {
                                        System.out.println("Syntax error: Line " + inLineCnt +
                                                        " Invalid label.");
                                        System.exit(1);
                                }
                                //Add label to the symbol table
                                if (!symbolTable.containsKey(token)) {
                                        symbolTable.put(token, lineCount);
                                } else {
                                        System.out.println("Syntax error: Line " + inLineCnt +
                                        " Invalid label reuse.");
                                        System.exit(1);
                                }
                        }
                        //Check any following token for a mnemonic
                        if (scanLine.hasNext()) {
                                token = scanLine.next();
                                //System.out.println(token);
                                if (opcodeTable.containsKey(token.toLowerCase())) {
                                        int jj;
                                        jj = doMnemonicTranslation(inLineCnt, lineCount, token.toLowerCase(),
                                                        scanLine, line);
                                        return lineCount + jj;
                                } else {
                                        //Check for comments, else print error
                                        if (token.startsWith(";") || token.startsWith("//")) {
                                                //it's a comment, ignore it
                                        } else {
                                                System.out.println("Syntax error: Line " + inLineCnt +
                                                " Mnemonic, comment, or label expected.");
                                                System.exit(1);
                                        }
                                }
                        }
                       
                }
                return lineCount;
        }




        /**
         * Helper method to translate a single line, given a valid mnemonic and the scanner
         * pointing after the mnemonic. Adds a line to the outputText, and fills in the
         * branchTable if necessary.
         * @param lineNum The line number of the output file (address)
         * @param mnemonic
         * @param scanLine Scanner pointing after the mnemonic
         * @return returns the number of output lines created.
         */
        private static int doMnemonicTranslation(int inLnCt, int lineNum, String mnemonic,
                                                                                        Scanner scanLine, String line) {
                String mn = mnemonic;
                String rs;
                String rt;
                String rd;
                String rd2;
                //int offset;
                //String target;
                //String comment = "";
                String outCode = "";
                int numOutLines = 0;
                //System.out.println(mn);
                try {
                        if (mn.equals("add") || mn.equals("sub") || mn.equals("and") || mn.equals("or")
                                        || mn.equals("nor")) {  // All 3 operand instructions
                                //System.out.println("Caught mnemonic");
                                String[] temp = scanLine.nextLine().split(",|//|;",4);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 3) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                                rt = temp[2].trim();
                                //comment = (temp.length >= 4 ? temp[3] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rs), 5, false)
                                             + toBits(regAliasTable.get(rt), 5, false)
                                             + toBits(regAliasTable.get(rd), 5, false)
                                             + "00000" + functTable.get(mn);
                                //System.out.println(outCode);
                                numOutLines = 1;
                        }   else if( mn.equals("mul"))  {
                                String[] temp = scanLine.nextLine().split(",|//|;",5);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 4) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rd2 = temp[1].trim();
                                rs = temp[2].trim();
                                rt = temp[3].trim();
                                //comment = (temp.length >= 4 ? temp[3] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rs), 5, false)
                                             + toBits(regAliasTable.get(rt), 5, false)
                                             + toBits(regAliasTable.get(rd), 5, false)
                                             + toBits(regAliasTable.get(rd2), 5, false)
                                             + functTable.get(mn);
                                //System.out.println(outCode);
                                numOutLines = 1;                                                                                                                                                                                        
                               
                        }                                                                                      
                       
                        else if (mn.equals("sll") || mn.equals("srl") || mn.equals("sra")
                           ||mn.equals("sllv") || mn.equals("srlv") || mn.equals("srav")) {
                                String[] temp = scanLine.nextLine().split(",|//|;",4);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 3) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                                rt = temp[2].trim();
                                //comment = (temp.length >= 4 ? temp[3] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                if (mn.equals("sll") || mn.equals("srl") || mn.equals("sra")) {                                
                                   
                                      if(mn.equals("sra"))        
                                             rt = toBits((new Integer(rt)) - 1, 5, true);
                                     else
                                         rt = toBits((new Integer(rt)) - 1, 5, false);
                                   if (rt == null) {
                                         System.out.println("Error: Line " + inLnCt + ", value out of range.");
                                       }                                
                               
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rs), 5, false)
                                             + "00000" + toBits(regAliasTable.get(rd), 5, false)
                                             + rt + functTable.get(mn);
                                }
                               
// sllv, slav, srav Instructions...                            
                                //else {
                              else{
                                          outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rs), 5, false)
                                             + toBits(regAliasTable.get(rt), 5, false)
                                             + toBits(regAliasTable.get(rd), 5, false)
                                             + "00000" + functTable.get(mn);                                                                                                                      
                                         
                                      }
                               
                               
                                        //System.out.println(outCode);
                                numOutLines = 1;
                        }
                       
                               
                               
                               
                        else if (mn.equals("addi")||mn.equals("ori")) {
                                String[] temp = scanLine.nextLine().split(",|//|;",4);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 3) {
                                        throw new NoSuchElementException();
                                }
                                rt = temp[0].trim();
                                rs = temp[1].trim();
                                rd = temp[2].trim();
                                //comment = (temp.length >= 3 ? temp[2] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                               
                                if(mn.equals("addi"))  {                                        
                                rd = toBits(new Integer(rd), 16, true);
                                if (rd == null) {
                                        System.out.println("Error: Line " + inLnCt + ", value out of range.");
                                   }                            
                                }
                                else {
                                        rd = toBits(new Integer(rd), 16, false);
                                if (rd == null) {
                                        System.out.println("Error: Line " + inLnCt + ", value out of range.");
                                   }
                                }
                               
                                outCode = opcodeTable.get(mn)+  
                                             toBits(regAliasTable.get(rs), 5, false)
                                             + toBits(regAliasTable.get(rt), 5, false)+ rd;
                                //System.out.println(outCode);
                                numOutLines = 1;
                        } else if (mn.equals("li")) {
                            String[] temp = scanLine.nextLine().split(",|//|;",4);
                            //System.out.println("temp.length " + temp.length);
                            if (temp.length < 2) {
                                    throw new NoSuchElementException();
                            }
                            rt = temp[0].trim();
                            rs = temp[1].trim();
                            //comment = (temp.length >= 3 ? temp[2] : "").trim();
                            //System.out.println("rd " + rd);
                            //System.out.println("rs " + rs);
                            //System.out.println("rt " + rt);
                            //System.out.println("rem " + comment);
                            rs = toBits(new Integer(rs), 16, false);
                            if (rs == null) {
                         System.out.println("Error: Line " + inLnCt + ", value out of range.");
                            }
                            outCode =    opcodeTable.get(mn)
                                         + "00000"+ toBits(regAliasTable.get(rt), 5, false)
                                          + rs;
                            //System.out.println(outCode);
                            numOutLines = 1;
                    }
                       
                       
                        else if (mn.equals("lui")) {
                                String[] temp = scanLine.nextLine().split(",|//|;",4);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rt = temp[0].trim();
                                rs = temp[1].trim();
                                //comment = (temp.length >= 3 ? temp[2] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                rs = toBits(new Integer(rs), 16, false);
                                if (rs == null) {
                                        System.out.println("Error: Line " + inLnCt + ", value out of range.");
                                }
                                outCode = opcodeTable.get(mn)+ "00000"
                                             + toBits(regAliasTable.get(rt), 5, false)
                                             + rs;
                                //System.out.println(outCode);
                                numOutLines = 1;
                        } else if (mn.equals("load") || mn.equals("store")) {
                                String[] temp = scanLine.nextLine().split(",",2);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rt = temp[0].trim();
                                temp = temp[1].split("\\(|\\)|//|;");
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                                //comment = (temp.length >= 4 ? temp[3] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                rd = toBits(new Integer(rd), 16, true);
                                if (rd == null) {
                                        System.out.println("Error: Line " + inLnCt + ", value out of range.");
                                }
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rs), 5, false)
                                             + toBits(regAliasTable.get(rt), 5, false)
                                             + rd;
                                //System.out.println(outCode);
                                numOutLines = 1;
                        } else if (mn.equals("bez") || mn.equals("blz")||mn.equals("blez")
                                ||mn.equals("la")||mn.equals("bgz")||mn.equals("bgez")||mn.equals("bnez")) {
                                String[] temp = scanLine.nextLine().split(",|//|;",4);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rs = temp[0].trim();
                                rd = temp[1].trim();
                                //comment = (temp.length >= 3 ? temp[2] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rs), 5, false) +                                            
                                             functTable.get(mn)+ "#target#";
                                Object[] bTableE = {new Integer(lineNum), rd};
                                branchTable.add(bTableE);
                                //System.out.println(outCode);
                                numOutLines = 1;
                        } else { // mn must be jalr
                                String[] temp = scanLine.nextLine().split(",|//|;",4);
                                //System.out.println("temp.length " + temp.length);
                                if (temp.length < 2) {
                                        throw new NoSuchElementException();
                                }
                                rd = temp[0].trim();
                                rs = temp[1].trim();
                                //comment = (temp.length >= 3 ? temp[2] : "").trim();
                                //System.out.println("rd " + rd);
                                //System.out.println("rs " + rs);
                                //System.out.println("rt " + rt);
                                //System.out.println("rem " + comment);
                                outCode = opcodeTable.get(mn)
                                             + toBits(regAliasTable.get(rs), 5, false) + "00000"
                                             + toBits(regAliasTable.get(rd), 5, false)
                                             +"00000"+ functTable.get(mn);
                                //System.out.println(outCode);
                                numOutLines = 1;
                        }
                        outputText.add(outCode + "\t" + line.trim() );
                } catch (NoSuchElementException e) {
                        System.out.println("Syntax error: Line " + inLnCt + ", operand expected.");
                        System.exit(1);
                } catch (NullPointerException e) {
                        System.out.println("Syntax error: Line " + inLnCt + ", operand error.");
                        System.exit(1);
                }
                return numOutLines;
               
        }
       
       
        /**
         * Helper method. Creates a string of bits given an integer and the length.
         * Either sign-extends the string.
         * @param integer The number to be converted to bits
         * @param length The length of the string to be created
         * @param signed If true, the number will be sign-extended
         * @return Returns a null if it cannot be represented in the specified length
         */
        private static String toBits(Integer integer, int length, boolean signed) {
                String out = Integer.toBinaryString(integer);
                if (signed) {
                        if (integer < 0) {
                                if (integer < (-1 * (int)Math.pow(2, (length - 1)))) {
                                        return null;
                                } else {
                                        out = out.substring(out.length() - length);
                                }
                        } else {        //Positive signed
                                if (integer > (int)Math.pow(2,(length - 1)) - 1) {
                                        return null;
                                } else {
                                        out = charSeq('0', length-out.length()) + out;
                                }
                        }
                } else { //Unsigned
                        if (integer < 0 || integer > (int)Math.pow(2,(length)) - 1) {
                                return null;
                        } else {
                                out = charSeq('0', length-out.length()) + out;
                        }
                }
                return out;
        }
       
        /**
         * Returns a string composed of ch repeated num times
         * @param ch
         * @param num
         */
        private static String charSeq(char ch, int num) {
                String out = "";
                for (int ii = 0 ; ii < num ; ii++) {
                        out = out + ch;
                }
                return out;
        }




       
        /**
         * Fills in the tables for the opcodes and register aliases
         */
        private static void initTables() {
                opcodeTable = new TreeMap<String, String>();
                regAliasTable = new TreeMap<String, Integer>();
                functTable = new TreeMap<String, String>();
                // Add supported instructions here. First field is opcode, second field is funct
                opcodeTable.put("add",  "000000");
                opcodeTable.put("sub",  "000000");
                opcodeTable.put("and",  "000000");
                opcodeTable.put("or",   "000000");
                opcodeTable.put("nor",  "000000");
                opcodeTable.put("mul",  "000000");
                opcodeTable.put("sll",  "000000");
                opcodeTable.put("srl",  "000000");
                opcodeTable.put("sra",  "000000");
                opcodeTable.put("sllv", "000000");
                opcodeTable.put("srlv", "000000");
                opcodeTable.put("srav", "000000");              
                opcodeTable.put("lui",  "001111");
                opcodeTable.put("li",   "001101");
                opcodeTable.put("addi", "001000");
                opcodeTable.put("ori",  "001101");
                opcodeTable.put("load", "100011");
                opcodeTable.put("store","101011");              
                opcodeTable.put("bez",  "000001");
                opcodeTable.put("blz",  "000001");
                opcodeTable.put("blez", "000001");
                opcodeTable.put("bgz",  "000001");
                opcodeTable.put("bgez", "000001");
                opcodeTable.put("bnez", "000001");      
                opcodeTable.put("jalr", "000000");
                opcodeTable.put("nop",  "000000");
                opcodeTable.put("la", "001000");  // same opcode as addi....
                // funct table for all the mnemonics
               
                functTable.put("add",   "100000");
                functTable.put("sub",   "100010");
                functTable.put("and",   "100100");
                functTable.put("or",    "100101");
                functTable.put("nor",   "100111");
                functTable.put("mul",   "011000");
                functTable.put("sll",   "000000");
                functTable.put("srl",   "000010");
                functTable.put("sra",   "000011");
                functTable.put("sllv",  "000100");
                functTable.put("srlv",  "000110");
                functTable.put("srav",  "000111");              
                functTable.put("bez",   "00000");
                functTable.put("blz",   "00001");
                functTable.put("blez",  "00010");
                functTable.put("bgz",   "00011");
                functTable.put("bgez",  "00100");
                functTable.put("bnez",  "00101");      
                functTable.put("jalr",  "001001");
                functTable.put("la",  "000000");
                functTable.put("nop",   "000000");
               
               
               
                // All aliases for registers
                regAliasTable.put("r0", 0);
                regAliasTable.put("r1", 1);
                regAliasTable.put("r2", 2);
                regAliasTable.put("r3", 3);
                regAliasTable.put("r4", 4);
                regAliasTable.put("r5", 5);
                regAliasTable.put("r6", 6);
                regAliasTable.put("r7", 7);
                regAliasTable.put("r8", 8);
                regAliasTable.put("r9", 9);
                regAliasTable.put("r10", 10);
                regAliasTable.put("r11", 11);
                regAliasTable.put("r12", 12);
                regAliasTable.put("r13", 13);
                regAliasTable.put("r14", 14);
                regAliasTable.put("r15", 15);
                regAliasTable.put("r16", 16);
                regAliasTable.put("r17", 17);
                regAliasTable.put("r18", 18);
                regAliasTable.put("r19", 19);
                regAliasTable.put("r20", 20);
                regAliasTable.put("r21", 21);
                regAliasTable.put("r22", 22);
                regAliasTable.put("r23", 23);
                regAliasTable.put("r24", 24);
                regAliasTable.put("r25", 25);
                regAliasTable.put("r26", 26);
                regAliasTable.put("r27", 27);
                regAliasTable.put("r28", 28);
                regAliasTable.put("r29", 29);
                regAliasTable.put("r30", 30);
                regAliasTable.put("r31", 31);
               
        }

       
       
}

